#include "ExactExtract.h"
#include "GDALCPP.h"
#include "OCMain/FileUtil.h"
#include "OCMain/ArgumentParser.h"

#include "exactextract/deferred_gdal_writer.h"
#include "exactextract/feature_sequential_processor.h"
#include "exactextract/gdal_dataset_wrapper.h"
#include "exactextract/gdal_raster_wrapper.h"
#include "exactextract/gdal_writer.h"
#include "exactextract/operation.h"
#include "exactextract/processor.h"
#include "exactextract/raster_sequential_processor.h"
#include "exactextract/utils.h"
#include "exactextract/utils_cli.h"

using exactextract::GDALDatasetWrapper;
using exactextract::GDALRasterWrapper;
using exactextract::Operation;

static GDALDatasetWrapper
load_dataset(const std::string& descriptor,
    const std::vector<std::string>& include_cols,
    const std::string& src_id_name,
    const std::string& dst_id_name,
    const std::string& dst_id_type);

namespace OCPP
{
	int ExactExtract::run(int argc, char** argv)
	{
        ArgumentParser arguments(&argc, argv);

        std::string poly_descriptor, src_id_name, output_filename, strategy, dst_id_type, dst_id_name;
        std::vector<std::string> stats;
        std::vector<std::string> raster_descriptors;
        std::vector<std::string> weight_descriptors;
        std::vector<std::string> include_cols;
        size_t max_cells_in_memory = 30;

        bool progress = false;
        bool nested_output = false;
        bool include_geom = false;

        //app.add_option("-p,--polygons", poly_descriptor, "polygon dataset")->required(true);
        arguments.getApplicationUsage()->addCommandLineOption("-p,--polygons", "polygon dataset");
        arguments.read("-p", poly_descriptor);
        //app.add_option("-r,--raster", raster_descriptors, "raster dataset")->required(true);
        arguments.getApplicationUsage()->addCommandLineOption("-r,--raster", "raster dataset");
        String rasterStr;
        while (arguments.read("-r", rasterStr))
        {
            raster_descriptors.push_back(rasterStr);
        }
        // 
        // 
        //app.add_option("-w,--weights", weight_descriptors, "weighting dataset")->required(false);
        // 
        //app.add_option("-f,--fid", src_id_name, "id from polygon dataset to retain in output")->required(false)
        arguments.read("-f", src_id_name);

        // 
        //app.add_option("-o,--output", output_filename, "output filename")->required(true);
        arguments.read("-o", output_filename);

        //app.add_option("-s,--stat", stats, "statistics")->required(false)->expected(-1);
        String staStr;
        while (arguments.read("-s", staStr))
        {
            stats.push_back(staStr);
        }

        //app.add_option("--max-cells", max_cells_in_memory, "maximum number of raster cells to read in memory at once, in millions")->required(false)->default_val("30");
        //app.add_option("--strategy", strategy, "processing strategy")->required(false)->default_val("feature-sequential");
        if (!arguments.read("-strategy", strategy))
        {
            strategy = "feature-sequential";
        }

        //app.add_option("--id-type", dst_id_type, "override type of id field in output")->required(false);
        //app.add_option("--id-name", dst_id_name, "override name of id field in output")->required(false);
        //app.add_flag("--nested-output", nested_output, "nested output");
        //app.add_option("--include-col", include_cols, "columns from input to include in output");
        String colStr;
        while (arguments.read("--include-col", colStr))
        {
            include_cols.push_back(colStr);
        }

        // 
        //app.add_flag("--include-geom", include_geom, "include geometry in output");
        arguments.read("--include-geom", include_geom);

        //app.add_flag("--progress", progress);
        //app.set_config("--config");

        if (raster_descriptors.empty()) {
            //std::cout << app.help();
            arguments.getApplicationUsage()->write(std::cout);
            return 0;
        }
        //CLI11_PARSE(app, argc, argv)

        if (dst_id_name.empty() != dst_id_type.empty()) {
            std::cerr << "Must specify both --id_type and --id_name" << std::endl;
            return 1;
        }

        if (src_id_name.empty() && !dst_id_name.empty()) {
            src_id_name = dst_id_name;
        }

        max_cells_in_memory *= 1000000;

        std::unique_ptr<exactextract::Processor> proc;
        std::unique_ptr<exactextract::OutputWriter> writer;

        try {
            //putenv("CPL_ENABLE_UTF8=YES");
            CPLSetConfigOption("SHAPE_ENCODING", "UTF-8");
            GDALAllRegister();
            OGRRegisterAll();

            auto rasters = exactextract::load_gdal_rasters(raster_descriptors);
            auto weights = exactextract::load_gdal_rasters(weight_descriptors);

            auto operations = prepare_operations(stats, rasters, weights);

            GDALDatasetWrapper shp = load_dataset(poly_descriptor, include_cols, src_id_name, dst_id_name, dst_id_type);

            std::unique_ptr<exactextract::GDALWriter> gdal_writer = std::make_unique<exactextract::GDALWriter>(
                output_filename, !nested_output, shp.srs());

            if (!dst_id_name.empty()) {
                include_cols.insert(include_cols.begin(), dst_id_name);
            }
            else if (!src_id_name.empty()) {
                include_cols.insert(include_cols.begin(), src_id_name);
            }

            if (include_cols.empty())
            {
                include_cols = gdal_writer->copy_field(shp);
            }
            else
            {
                for (const auto& field : include_cols) {
                    gdal_writer->copy_field(shp, field);
                }
            }



            writer = std::move(gdal_writer);

            if (strategy == "feature-sequential") {
                proc = std::make_unique<exactextract::FeatureSequentialProcessor>(shp, *writer);
            }
            else if (strategy == "raster-sequential") {
                proc = std::make_unique<exactextract::RasterSequentialProcessor>(shp, *writer);
            }
            else {
                throw std::runtime_error("Unknown processing strategy: " + strategy);
            }

            for (const auto& op : operations) {
                proc->add_operation(*op);
            }

            for (const auto& field : include_cols) {
                proc->include_col(field);
            }

            if (include_geom) {
                proc->include_geometry();
            }

            proc->set_max_cells_in_memory(max_cells_in_memory);
            proc->show_progress(progress);

            proc->process();
            writer->finish();

            return 0;
        }
        catch (const std::exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;

            return 1;
        }
        catch (...) {
            std::cerr << "Unknown error." << std::endl;

            return 1;
        }
	}
}


static GDALDatasetWrapper
load_dataset(const std::string& descriptor,
    const std::vector<std::string>& include_cols,
    const std::string& src_id_name,
    const std::string& dst_id_name,
    const std::string& dst_id_type)
{
    const auto parsed = exactextract::parse_dataset_descriptor(descriptor);

    std::vector<std::string> select;

    if (!src_id_name.empty()) {
        std::string id_select;

        if (!dst_id_type.empty()) {
            id_select += "CAST(";
        }
        id_select += src_id_name;

        if (!dst_id_type.empty()) {
            id_select += " AS " + dst_id_type + ")";
        }

        if (!dst_id_name.empty()) {
            id_select += " AS " + dst_id_name + "";
        }

        select.push_back(id_select);
    }

    for (const auto& col : include_cols) {
        select.push_back(col);
    }

    auto ds = GDALDatasetWrapper{ parsed.first, parsed.second };

    if (!select.empty()) {
        ds.set_select(select);
    }

    return ds;
}
